package me.android.library.starters.http;

import android.annotation.SuppressLint;
import android.net.Uri;

import androidx.annotation.NonNull;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import me.android.library.common.service.AbstractService;
import me.android.library.utils.android.StorageUtil;
import mylab.base.foundation.lang.Callback;
import mylab.utils.common.FileUtil;
import okhttp3.Call;
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.jackson.JacksonConverterFactory;

@Slf4j
public class RestfulService extends AbstractService {
    private static class SingletonHolder {
        @SuppressLint("StaticFieldLeak")
        private final static RestfulService instance = new RestfulService();
    }

    public static RestfulService getInstance() {
        return SingletonHolder.instance;
    }


    @Getter
    @Setter
    private String defaultHost;

    @Getter
    @Setter
    private OkHttpClient client;

    @Getter
    @Setter
    private ObjectMapper objectMapper;
    private final Map<String, List<Cookie>> cookieStore = Maps.newConcurrentMap();
    private final Map<String, Retrofit> map = Maps.newConcurrentMap();

    private RestfulService() {
    }

    @Override
    public void init() {
        super.init();

        this.objectMapper = defaultObjectMapper();
        this.client = defaultOkHttpClient();
    }

    public <T> T createApi(Class<T> clazz) {
        return createApi(defaultHost, clazz);
    }

    /**
     * 创建Restful Client Service 实例
     *
     * @param endpoint 服务端点，形如：ip:port
     * @param clazz    Restful Interface 定义
     * @return Restful Service 实例
     */
    public <T> T createApi(String endpoint, Class<T> clazz) {
        Retrofit retrofit = getRetrofit(endpoint);
        return retrofit.create(clazz);
    }

    synchronized private Retrofit getRetrofit(String endpoint) {
        if (map.containsKey(endpoint)) {
            return map.get(endpoint);
        } else {
            Retrofit.Builder builder = new Retrofit.Builder()
                    .baseUrl(endpoint)
                    .client(client)
                    .addConverterFactory(JacksonConverterFactory.create(objectMapper));

            Retrofit retrofit = builder.build();
            map.put(endpoint, retrofit);
            return retrofit;
        }
    }

    public void downFile(final String url,
                         final String fileName,
                         final Callback<Uri> callback) {
        Request request = new Request.Builder().url(url).build();
        client.newCall(request).enqueue(new okhttp3.Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                Callback.dealOnException(callback, e);
            }

            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                assert response.body() != null;
                InputStream in = response.body().byteStream();

                String filePath = String.format("%s/%s", StorageUtil.getCachPath(), fileName);
                FileUtil.writeFromStream(in, filePath);

                Uri uri = Uri.fromFile(new File(filePath));
                Callback.dealOnSuccess(callback, uri);
            }
        });

    }


    public ObjectMapper defaultObjectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        return objectMapper;
    }

    public OkHttpClient defaultOkHttpClient() {
        HttpLoggingInterceptor loggingInterceptor = defaultLoggingInterceptor();
        Interceptor headerInterceptor = defaultHeadInterceptor();
        CookieJar cookieJar = deaultCookieJar();
        return newOkHttpClient(loggingInterceptor, headerInterceptor, cookieJar,
                10, 30, 30, true);
    }


    public OkHttpClient newOkHttpClient(HttpLoggingInterceptor loggingInterceptor,
                                        Interceptor headerInterceptor,
                                        CookieJar cookieJar,
                                        long connectTimeoutSeconds,
                                        long writeTimeoutSeconds,
                                        long readTimeoutSeconds,
                                        boolean retryOnConnectionFailure) {

        return new OkHttpClient.Builder()
                .cookieJar(cookieJar)
                .addInterceptor(loggingInterceptor)
                .addInterceptor(headerInterceptor)
                .connectTimeout(connectTimeoutSeconds, TimeUnit.SECONDS)
                .writeTimeout(writeTimeoutSeconds, TimeUnit.SECONDS)
                .readTimeout(readTimeoutSeconds, TimeUnit.SECONDS)
                .retryOnConnectionFailure(retryOnConnectionFailure)
                .build();

    }

    public HttpLoggingInterceptor defaultLoggingInterceptor() {
        return newLoggingInterceptor(HttpLoggingInterceptor.Level.BODY);
    }

    public HttpLoggingInterceptor newLoggingInterceptor(HttpLoggingInterceptor.Level level) {
        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        loggingInterceptor.level(level);
        return loggingInterceptor;
    }

    public Interceptor defaultHeadInterceptor() {
        return chain -> {
            // 以拦截到的请求为基础创建一个新的请求对象，然后插入Header
            Request request = chain.request().newBuilder()
                    .addHeader("Content-Type", "application/json")
                    .addHeader("Accept", "application/json")
                    .build();
            // 开始请求
            return chain.proceed(request);
        };
    }

    public CookieJar deaultCookieJar() {
        return new CookieJar() {
            @Override
            public void saveFromResponse(@NonNull HttpUrl url, @NonNull List<Cookie> cookies) {
                cookieStore.put(url.host(), cookies);
            }

            @NonNull
            @Override
            public List<Cookie> loadForRequest(@NonNull HttpUrl url) {
                List<Cookie> cookies = cookieStore.get(url.host());
                return cookies != null ? cookies : Lists.newArrayList();
            }
        };
    }

}
